package my1024.controller;

import java.util.ArrayList;
import java.util.Random;

import my1024.app.Const;
import my1024.model.BoxModel;
import my1024.utils.EventType;

/**
 * minhvv, p18, IFI, TPE 01/2014 - 07/2014
 */

public class LogicBoard {
	private long[][] board = null;
	private LogicController controller;
	private Random rnd;
	private static int autoId = 0;

	public LogicBoard(LogicController controller) {
		this.controller = controller;
		board = new long[Const.MAX_ROW][Const.MAX_COL];
		rnd = new Random();
	}

	private int valueOf(long val) {
		return (int) (val >> 32);
	}

	private int idOf(long val) {
		return (int) val;
	}

	private long pack(int value, int id) {
		long val = value;
		return (val << 32) | id;
	}

	public void initBoard() {
		genRandomPos();
		genRandomPos();
	}

	public void genRandomPos() {
		ArrayList<String> availablePos = buildListAvailablePos();
		if (availablePos.isEmpty()) {
			controller.runOutOfSpace();
			return;
		}

		int index = rnd.nextInt(availablePos.size());
		String[] newPos = availablePos.get(index).split("-");
		if (2 != newPos.length) {
			System.out.println("Gen random pos error!");
			return;
		}

		int row = Integer.parseInt(newPos[0]);
		int col = Integer.parseInt(newPos[1]);
		int value = (Math.random() < 0.5) ? 2 : 4;
		autoId++;
		board[row][col] = pack(value, autoId);

		BoxModel box = new BoxModel(autoId, value, row, col);
		controller.addRandomBox(box);
	}

	private ArrayList<String> buildListAvailablePos() {
		ArrayList<String> availablePos = new ArrayList<String>();
		for (int col = 0; col < Const.MAX_COL; ++col) {
			if (0 == board[0][col]) {
				availablePos.add("0-" + col);
			}
			if (0 == board[Const.MAX_ROW - 1][col]) {
				availablePos.add((Const.MAX_ROW - 1) + "-" + col);
			}
		}

		for (int row = 1; row < Const.MAX_ROW - 1; ++row) {
			if (0 == board[row][0]) {
				availablePos.add(row + "-0");
			}
			if (0 == board[row][Const.MAX_COL - 1]) {
				availablePos.add(row + "-" + (Const.MAX_COL - 1));
			}
		}
		return availablePos;
	}

	private void flipVertical() {
		for (int row = 0; row < Const.MAX_ROW; ++row) {
			for (int col = 0; col < Const.MAX_COL / 2; ++col) {
				int targetCol = Const.MAX_COL - 1 - col;
				board[row][col] = board[row][col] ^ board[row][targetCol];
				board[row][targetCol] = board[row][col] ^ board[row][targetCol];
				board[row][col] = board[row][col] ^ board[row][targetCol];
			}
		}
	}

	private void flipHorizontal() {
		for (int col = 0; col < Const.MAX_COL; ++col) {
			for (int row = 0; row < Const.MAX_ROW / 2; ++row) {
				int targetRow = Const.MAX_ROW - 1 - row;
				board[row][col] = board[row][col] ^ board[targetRow][col];
				board[targetRow][col] = board[row][col] ^ board[targetRow][col];
				board[row][col] = board[row][col] ^ board[targetRow][col];
			}
		}
	}

	private void rotate90Left() {
		int MAX_SIZE = Const.MAX_ROW;
		long[][] rotatedBoard = new long[MAX_SIZE][MAX_SIZE];

		for (int col = MAX_SIZE - 1; col >= 0; --col) {
			int targetRow = MAX_SIZE - 1 - col;
			for (int row = 0; row < MAX_SIZE; ++row) {
				int targetCol = row;
				rotatedBoard[targetRow][targetCol] = board[row][col];
			}
		}

		board = rotatedBoard;
	}

	private void rotate90Right() {
		int MAX_SIZE = Const.MAX_ROW;
		long[][] rotatedBoard = new long[MAX_SIZE][MAX_SIZE];

		for (int row = 0; row < MAX_SIZE; ++row) {
			int targetCol = MAX_SIZE - 1 - row;
			for (int col = 0; col < MAX_SIZE; ++col) {
				int targetRow = col;
				rotatedBoard[targetRow][targetCol] = board[row][col];
			}
		}

		board = rotatedBoard;
	}

	private void slideToRight() {
		for (int row = 0; row < Const.MAX_ROW; ++row) {
			// find first empty place for each element from right to left
			for (int col = Const.MAX_COL - 1; col >= 0; --col) {
				if (0 != board[row][col]) {
					// find the place from the current col to the end
					int targetCol = -1;
					for (int scanCol = col + 1; scanCol < Const.MAX_COL; ++scanCol) {
						if (0 == board[row][scanCol]) {
							targetCol = scanCol;
						}
					}

					if (targetCol > -1) {
						board[row][targetCol] = board[row][col];
						board[row][col] = 0;

						// find out moving objects
						int boxId = idOf(board[row][targetCol]);
						int value = valueOf(board[row][targetCol]);
						BoxModel oldBox = new BoxModel(boxId, value, row, col);
						BoxModel newBox = new BoxModel(boxId, value, row, targetCol);
						controller.addChangeBox(EventType.MOVE_BOX, oldBox, newBox);
					}
				}
			}
		}
	}

	private void mergeToRight() {
		for (int row = 0; row < Const.MAX_ROW; ++row) {
			int mergeCol = Const.MAX_COL - 1;
			while (mergeCol > 0) {
				if (0 != board[row][mergeCol]) {
					int deleteCol = mergeCol - 1;
					int mergeId = idOf(board[row][mergeCol]);
					int mergeValue = valueOf(board[row][mergeCol]);
					int deleteId = idOf(board[row][deleteCol]);
					int deleteValue = valueOf(board[row][deleteCol]);

					if (mergeValue == deleteValue) {
						board[row][mergeCol] = pack(2 * mergeValue, mergeId);

						// find out changed objects
						BoxModel oldMergeBox = new BoxModel(mergeId, mergeValue, row, mergeCol);
						BoxModel newMergeBox = new BoxModel(mergeId, 2 * mergeValue, row,
								mergeCol);
						controller.addChangeBox(EventType.CHANGE_BOX, oldMergeBox, newMergeBox);

						board[row][deleteCol] = 0;
						// find out deleted objects
						BoxModel deleteBox = new BoxModel(deleteId, deleteValue, row, deleteCol);
						controller.addDeleteBox(deleteBox);

						// jump over a deleted column
						mergeCol = deleteCol;
					}
				}

				// scan to next column to check if it can merge
				mergeCol--;
			}
		}
	}

	private void processMainLogic() {
		slideToRight();
		mergeToRight();
		slideToRight();
	}

	public void processMoveRight() {
		processMainLogic();
	}

	public void processMoveLeft() {
		flipVertical();
		processMainLogic();
		flipVertical();
	}

	public void processMoveUp() {
		rotate90Right();
		processMainLogic();
		rotate90Left();
	}

	public void processMoveDown() {
		rotate90Left();
		processMainLogic();
		rotate90Right();
	}

	public void dump() {
		StringBuilder sb = new StringBuilder();
		for (int row = 0; row < Const.MAX_ROW; ++row) {
			for (int col = 0; col < Const.MAX_COL; ++col) {
				sb.append(valueOf(board[row][col]));
				// sb.append("-");
				// sb.append(idOf(board[row][col]));
				sb.append("\t");
			}
			sb.append("\n");
		}
		System.out.println(sb.toString());
	}
}
